﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using GUIServerConnector;
using Server.TcpIp;
using ApplicationConfiguration;
using EntityModel;
using SensorClient;
using Logging;

namespace Server
{
    public class Server : StatusProvider
    {
        private static SimpleLogger logger = SimpleLogger.getLogger(typeof(Program));

        private MD5CryptoServiceProvider md5CryptoServiceProvider;
        private BinaryFormatter binaryFormatter;

        private TeleasistentaEntities entities;
        private TcpServerModel sensorServer;
        private IDictionary<DefinitieSenzori, PropertyInfo> sensorDefinitions;
        private DateTime startTime;
        private ICollection<StatusProvider> children;

        private object[][] sensorDefinitionsData;

        public Server()
        {
            md5CryptoServiceProvider = new MD5CryptoServiceProvider();
            binaryFormatter = new BinaryFormatter();
            children = new List<StatusProvider>();
            Type sensorContainerType = typeof(SensorContainer);
            sensorDefinitionsData = new object[][] {
                new object[] {sensorContainerType.GetProperty("Ecg"), "ECG", "-", -50D, 150D},
                new object[] {sensorContainerType.GetProperty("HtaSystolic"), "HTA Systolic", "mmHg", 20D, 300D},
                new object[] {sensorContainerType.GetProperty("HtaDiastolic"), "HTA Diastolic", "mmHg", 20D, 300D},
                new object[] {sensorContainerType.GetProperty("Pulse"), "Pulse", "BPM", 40D, 200D},
                new object[] {sensorContainerType.GetProperty("BodyTemperature"), "Body temperature", "`C", 30D, 43D},
                new object[] {sensorContainerType.GetProperty("Glicemia"), "Glicemia", "mmol/l", 10D, 400D},
                new object[] {sensorContainerType.GetProperty("RespiratoryFrequency"), "Respiratory frequency", "breaths/min", 5D, 40D},
                new object[] {sensorContainerType.GetProperty("NotEnoughLight"), "Not enough light", "-", 0D, 0D},
                new object[] {sensorContainerType.GetProperty("AmbientTemperature"), "Ambient temperature", "`C", -5D, 90D},
                new object[] {sensorContainerType.GetProperty("GasProblem"), "Gas problem", "-", 0D, 0D},
                new object[] {sensorContainerType.GetProperty("HumidityDanger"), "Humidity danger", "-", 0D, 0D},
                new object[] {sensorContainerType.GetProperty("RfidEmptyRoom"), "RFID empty room", "-", 0D, 0D},
                new object[] {sensorContainerType.GetProperty("PowerDown"), "Power down", "-", 0D, 0D},
                new object[] {sensorContainerType.GetProperty("Weight"), "Weight", "kg", 30D, 300D}
            };
        }

        public void start()
        {
            DateTime start = DateTime.Now;

            logger.info("Starting server...");
            logger.info("Reading server configuration...");
            AppConfiguration appConfiguration = AppConfiguration.getInstance();

            logger.info("Connecting to database...");
            entities = new TeleasistentaEntities(appConfiguration.getProperty("Database", "connectionString"));
            logger.info("Database connection successful");

            logger.info("Registering manager user if needed...");
            if (registerManagerUser())
                logger.info("Manager user succesfully registered");
            else
                logger.info("Manager user registration not needed");

            logger.info("Registering emulator pacient if needed...");
            if (registerEmulatorUser())
                logger.info("Emulator pacient succesfully registered");
            else
                logger.info("Emulator pacient registration not needed");

            logger.info("Registering default sensor definitions if needed...");
            if (registerDefaultDefinitions())
                logger.info("Default sensor definitions succesfully registered");
            else
                logger.info("Default sensor definition registration not needed");

            logger.info("Starting sensor processing endpoint...");

            int sensorServerPort = Int32.Parse(appConfiguration.getProperty("SensorServer", "port"));

            sensorServer = new TcpServerModel(sensorServerPort);
            sensorServer.MsgReceived += new MsgReceivedEventHandler(SensorsReceived);
            sensorServer.Start();
            logger.info("Sensor processing endpoint started");
            children.Add(sensorServer);

            logger.info("Starting user interface services...");
            GUIServerConnectorController.startServices();
            logger.info("User interface services started");

            DateTime end = DateTime.Now;
            TimeSpan startupTime = end.Subtract(start);
            logger.info("Server startup completed in " + startupTime);
            startTime = DateTime.Now;
        }

        private bool registerManagerUser()
        {
            if (entities.Users.SingleOrDefault(u => u.Rol.Equals("MANAGER")) == null)
            {
                User managerUser = new User();
                managerUser.idUser = Guid.NewGuid();
                managerUser.Username = "admin";
                managerUser.Password = md5CryptoServiceProvider.ComputeHash(Encoding.ASCII.GetBytes("admin"));
                managerUser.Rol = "MANAGER";
                entities.Users.AddObject(managerUser);
                entities.SaveChanges();
                return true;
            }
            return false;
        }

        private bool registerEmulatorUser()
        {
            Guid emulatorPacientId = new Guid(md5CryptoServiceProvider.ComputeHash(Encoding.ASCII.GetBytes(AppConfiguration.getInstance().getProperty("Emulator", "moduleId"))));
            if (entities.Pacients.SingleOrDefault(p => p.idPacient.Equals(emulatorPacientId)) == null)
            {
                Pacient emulatorPacient = new Pacient();
                emulatorPacient.idPacient = emulatorPacientId;
                emulatorPacient.Nume = "SensorClient";
                emulatorPacient.Prenume = "Emulator";
                emulatorPacient.CNP = "3120522350000";
                emulatorPacient.Data_nasterii = DateTime.Now;
                emulatorPacient.Sex = "n";
                emulatorPacient.Telefon = "N/A";
                emulatorPacient.Adresa = "N/A";
                emulatorPacient.Email = "N/A";
                emulatorPacient.Istoric_medical = "N/A";
                emulatorPacient.Lista_alergi = "N/A";
                emulatorPacient.Masurari_fiziologice = "N/A";
                emulatorPacient.Recomandari_medicale = "N/A";

                emulatorPacient.User = new User();
                emulatorPacient.User.idUser = Guid.NewGuid();
                emulatorPacient.User.Username = AppConfiguration.getInstance().getProperty("Emulator", "userName");
                emulatorPacient.User.Password = md5CryptoServiceProvider.ComputeHash(Encoding.ASCII.GetBytes(AppConfiguration.getInstance().getProperty("Emulator", "password")));
                emulatorPacient.User.Rol = "PACIENT";

                entities.Pacients.AddObject(emulatorPacient);
                entities.SaveChanges();
                return true;
            }
            return false;
        }

        private bool registerDefaultDefinitions()
        {
            sensorDefinitions = new Dictionary<DefinitieSenzori, PropertyInfo>();

            bool changed = false;
            foreach (object[] sensorDefinitionData in sensorDefinitionsData)
            {
                string sensorType = sensorDefinitionData[1].ToString();
                DefinitieSenzori sensorDefinition = entities.DefinitieSenzoris.SingleOrDefault(ds => ds.Tip_Senzori.Equals(sensorType));
                if (sensorDefinition == null)
                {
                    sensorDefinition = new DefinitieSenzori();
                    sensorDefinition.idDefinitie = new Guid(md5CryptoServiceProvider.ComputeHash(Encoding.ASCII.GetBytes(sensorType)));
                    sensorDefinition.Tip_Senzori = sensorType;
                    sensorDefinition.Unitate_de_masura = sensorDefinitionData[2].ToString();
                    entities.DefinitieSenzoris.AddObject(sensorDefinition);
                    changed = true;
                }
                sensorDefinitions.Add(sensorDefinition, (PropertyInfo)sensorDefinitionData[0]);

                DefiniteAlarme alarmDefinition = entities.DefiniteAlarmes.SingleOrDefault(da => ((da.idPacient == null) && (da.idMedic == null)) && da.idDefinitieSenzor.Equals(sensorDefinition.idDefinitie));
                if (alarmDefinition == null)
                {
                    alarmDefinition = new DefiniteAlarme();
                    alarmDefinition.DenumireAlarma = sensorType + "_alarm_default";
                    alarmDefinition.idDefinitieAlarme = new Guid(md5CryptoServiceProvider.ComputeHash(Encoding.ASCII.GetBytes(alarmDefinition.DenumireAlarma)));
                    alarmDefinition.idDefinitieSenzor = sensorDefinition.idDefinitie;
                    alarmDefinition.idMedic = null;
                    alarmDefinition.idPacient = null;
                    alarmDefinition.NrOrdine = 0;
                    alarmDefinition.ValoarePragInferior = (Double)sensorDefinitionData[3];
                    alarmDefinition.ValoarePragMinim = (Double)sensorDefinitionData[3];
                    alarmDefinition.ValoarePragSuperior = (Double)sensorDefinitionData[4];
                    alarmDefinition.ValoarePragMaxim = (Double)sensorDefinitionData[4];
                    entities.DefiniteAlarmes.AddObject(alarmDefinition);
                    changed = true;
                }

            }
            if (changed)
                entities.SaveChanges();
            return changed;
        }

        private void SensorsReceived(object sender, MsgReceivedEventArgs e)
        {
            new Thread(handleSensorsReceived).Start(e);
        }

        //Concurrent processing :D YEAAHHH
        private void handleSensorsReceived(object eventArgs)
        {
            DateTime readTime = DateTime.Now;
            SensorContainer sensorContainer = SensorContainer.FromXmlString(((MsgReceivedEventArgs) eventArgs).Message);
            Guid pacientId = new Guid(md5CryptoServiceProvider.ComputeHash(Encoding.ASCII.GetBytes(sensorContainer.ModuleId.ToString())));
            Pacient pacient = entities.Pacients.SingleOrDefault(p => p.idPacient.Equals(pacientId));
            if (pacient == null)
            {
                logger.warning("Data received for unregistered pacient id " + pacientId + "!");
                return;
            }
            foreach (KeyValuePair<DefinitieSenzori, PropertyInfo> sensorDefinition in sensorDefinitions)
            {
                ValoriSensori sensorValue = new ValoriSensori();
                sensorValue.idDefinitie = sensorDefinition.Key.idDefinitie;
                sensorValue.idPacient = pacientId;
                sensorValue.idValoareSenzor = Guid.NewGuid();
                sensorValue.MementCitire = readTime;
                MemoryStream valueStream = new MemoryStream();
                binaryFormatter.Serialize(valueStream, sensorDefinition.Value.GetValue(sensorContainer, null));
                sensorValue.ValoareCitita = valueStream.ToArray();
                entities.ValoriSensoris.AddObject(sensorValue);

                IEnumerable<DefiniteAlarme> alarmDefinitions = sensorDefinition.Key.DefiniteAlarmes.Where(da => ((da.idPacient == null) && (da.idMedic == null)) || da.idPacient.Equals(pacientId));
                Type sensorValueType = sensorDefinition.Value.DeclaringType;
                foreach (DefiniteAlarme alarmDefinition in alarmDefinitions)
                {
                    bool alarm = false;
                    object looseTypeSensorValue = sensorDefinition.Value.GetValue(sensorContainer, null);
                    if (typeof(bool).IsInstanceOfType(looseTypeSensorValue))
                    {
                        alarm = (bool)sensorDefinition.Value.GetValue(sensorContainer, null);
                    }
                    else if (typeof(int).IsInstanceOfType(looseTypeSensorValue))
                    {
                        int actualSensorValue = (int)sensorDefinition.Value.GetValue(sensorContainer, null);
                        alarm = ((alarmDefinition.ValoarePragInferior > actualSensorValue) || (alarmDefinition.ValoarePragSuperior < actualSensorValue));
                    }
                    else if (typeof(float).IsInstanceOfType(looseTypeSensorValue))
                    {
                        float actualSensorValue = (float)sensorDefinition.Value.GetValue(sensorContainer, null);
                        alarm = ((alarmDefinition.ValoarePragInferior > actualSensorValue) || (alarmDefinition.ValoarePragSuperior < actualSensorValue));
                    }
                    else if (typeof(int[]).IsInstanceOfType(looseTypeSensorValue))
                    {
                        int[] actualSensorValues = (int[])sensorDefinition.Value.GetValue(sensorContainer, null);
                        int alarmCount = 0;
                        foreach (int actualSensorValue in actualSensorValues)
                            if ((alarmDefinition.ValoarePragInferior > actualSensorValue) || (alarmDefinition.ValoarePragSuperior < actualSensorValue))
                                alarmCount++;
                        alarm = ((alarmCount * 2) > actualSensorValues.Length);
                    }
                    if (alarm)
                    {
                        ValoriAlarme alarmValue = new ValoriAlarme();
                        alarmValue.idDefinitieAlarma = alarmDefinition.idDefinitieAlarme;
                        alarmValue.idValoareAlarma = Guid.NewGuid();
                        alarmValue.MomentulDeclansare = DateTime.Now;
                        alarmValue.MomentulConfirmarie = null;
                        alarmValue.idUserConfirmareAlarma = null;
                        alarmValue.ValoriSensoris.Add(sensorValue);
                        entities.ValoriAlarmes.AddObject(alarmValue);
                    }
                }
                entities.SaveChanges();
            }
        }

        public DateTime getStartTime()
        {
            return startTime;
        }

        public DateTime getLastAccessTime()
        {
            return startTime;
        }

        public ICollection<StatusProvider> getChildren()
        {
            return children;
        }

        public void stop()
        {
            logger.info("Stopping server...");

            logger.info("Stopping user interface services...");
            GUIServerConnectorController.stopServices();
            logger.info("User interface services stopped");

            logger.info("Stopping sensor processing endpoint...");
            sensorServer.Stop();
            logger.info("Sensor processing endpoint stopped");
            children.Remove(sensorServer);

            logger.info("Flushing database entity context...");
            entities.SaveChanges();
            logger.info("Closing database connections...");
            entities.Dispose();
            logger.info("Database connections closed");

            logger.info("Server shotdown complete");
        }
    }
}
